using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;
using System.Web;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using Dimok.Data.Database;
using Dimok.Exceptions;
using Dimok.Data;

////////////////////////////////////////////////////////////////////////////////////////////////////
// namespace: Dimok.BlackBook
//
// summary:	.
////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Dimok.BlackBook{

	#region Exceptions
	public class VinException:Exception{
		public VinException(string msg):base(msg){}
	}
	public class VinNotFoundException:VinException{
		public VinNotFoundException(string vin):base("VIN ["+vin+"] not found."){}
	}
	public class VinInvalidException:VinException{
		public VinInvalidException(string vin):base("VIN ["+vin+"] is not valid."){}
	}
	#endregion
	
	#region Data Classes/Structs
	public class PriceInfo{
		public int Price = 0;
		public string Type = "";
		public PriceInfo(){}
		public PriceInfo(int price,string type){
			Price = price;
			Type = type;
		}
		public static implicit operator string(PriceInfo pi){
			return "Price:"+pi.Price+",Type:"+pi.Type;
		}
	}
	public struct AddOns_BookValue{
		public string[][] addOns;
		public string[] bookValue;
		public AddOns_BookValue( string[][] addOns,string[] bookValue){
			this.addOns = addOns;
			this.bookValue = bookValue;
		}
	}
	public struct CarInfo{
		public string VIN;
		public		int Year;
		public string Model;
		public string Make;
		public string Series;
		public string BodyStyle;
		public		int Price;
		public string PriceDesc;
		public string ToString(string separator){
			string[] s = new string[7];
			s[0] = "VIN:"+VIN;
			s[1] = "Year:"+Year;
			s[2] = "Make:"+Make;
			s[3] = "Model:"+Model;
			s[4] = "Series:"+Series;
			s[5] = "BodyStyle:"+BodyStyle;
			s[6] = "Parice:"+Price+"("+PriceDesc+")";
			return string.Join(separator,s);
		}
	}
	public enum PriceType : int {ExtraClean=0,Clean,Average,Rough};
	#endregion
	
	/// <summary>
	/// Centralized single-threaded access to http://www.blackbookusa.com/
	/// </summary>
	public class BlackBook{
	
		#region Fields and Constructors
		private const string PROVIDER_CODE = "B";
		private const string KEY_FILE_NAME = "STEP_FILE_NAME";
		private const string FILE_NAME_PREFIX = "BB_";
		private const string BB_SessionID = "BB_SessionID";
		private const string BB_URL_ROOT = "http://www.blackbookusa.com/bb/products/";
		private const string BB_PATH_USED = "usedcar.asp";
		private const string HEADER_TABLE = "<TABLE(?:.|\\n)+?black\\s+?book\\s+?(?:Wholesale|Retail|Trade-In)(?:.|\\n)+?</TABLE>";
		private static string m_cntl = "273B4BdE1A1YB36B1529BAB";

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Default Constractor. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		protected BlackBook(){}
		#endregion Fields and Constructors

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Values that represent PriceType. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public enum PriceType {clean,average,rough};
		//private const string BB_PATH = "http://www.blackbookusa.com/bb/products/usedcarxmlportal.asp";
		//private const string BB_PATH = "http://www.blackbookusa.com/bb/portals/xmlportal.asp";

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Queries if the cars ds is empty. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="ApplicationException">	Thrown when application. </exception>
		///
		/// <param name="dsCars">	The ds cars. </param>
		///
		/// <returns>	true if the cars ds is empty, false if not. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static bool IsCarsDSEmpty(CarsDS dsCars){
			if( dsCars.car.Count > 1 )throw new ApplicationException("Too many ["+dsCars.car.Count+"] cars in DataSet!");
			if( dsCars.wholesale_value.Count > 1 )throw new ApplicationException("Too many ["+dsCars.wholesale_value.Count+"] wholesale values in DataSet!");
			return dsCars.car.Count == 0;
		}

		#region Load from CarsDS

			#region LoadPriceInfo

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="ClientException">	Thrown when client. </exception>
		///
		/// <param name="year">				The year. </param>
		/// <param name="make">				The make. </param>
		/// <param name="model">			The model. </param>
		/// <param name="series">			The series. </param>
		/// <param name="bodyStyle">	The body style. </param>
		/// <param name="addOns">			The add ons. </param>
		/// <param name="state">			The state. </param>
		/// <param name="mileage">		The mileage. </param>
		///
		/// <returns>	The price information. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS LoadPriceInfo(string year,string make,string model,string series,string bodyStyle,string[] addOns,string state,int mileage){
			try{
				PriceInfoDS dsPI;
				while(true){
					CarsDS dsCars = FetchCars(year+"",make,model,series,bodyStyle,state,mileage+"");
					try{
						dsPI = LoadPriceInfo(dsCars,addOns,state,mileage);
					}catch(NoMileageAddOnException){
						mileage -= 10000;
						continue;
					}
					break;
				}
				if( dsPI.CarInfo.Count == 0 )
					dsPI.CarInfo.AddCarInfoRow("","",year,make,model,series,bodyStyle,mileage,state,0,"","",PROVIDER_CODE);
				return dsPI;
			}catch(Exception exc){
				throw new ClientException(exc, "year", year, "make", make, "model", model, "series", series, bodyStyle, "bodystyle", "state", state, "mileage", mileage + "");
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="vin">			The vin. </param>
		/// <param name="state">		The state. </param>
		/// <param name="mileage">	The mileage. </param>
		///
		/// <returns>	The price information. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS LoadPriceInfo(string vin,string state,int mileage){
			CarsDS dsCars = FetchCars(vin,state,mileage+"");
			return LoadPriceInfo(dsCars,state,mileage);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">		The ds cars. </param>
		/// <param name="state">		The state. </param>
		/// <param name="mileage">	The mileage. </param>
		///
		/// <returns>	The price information. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS LoadPriceInfo(CarsDS dsCars,string state,int mileage){
			return LoadPriceInfo(dsCars,null,state,mileage);}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">		The ds cars. </param>
		/// <param name="addOns">		The add ons. </param>
		/// <param name="state">		The state. </param>
		/// <param name="mileage">	The mileage. </param>
		///
		/// <returns>	The price information. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS LoadPriceInfo(CarsDS dsCars,string[] addOns,string state,int mileage){
			if( dsCars.car.Count > 1 ){
				PriceInfoDS dsPIs = new PriceInfoDS();
				foreach( CarsDS.carRow drCar in dsCars.car )
					LoadCarInfo(drCar,dsPIs,state,mileage);
				return dsPIs;
			}
			PriceInfoDS dsPI = (PriceInfoDS)BlackBook.LoadAddOns(dsCars).DataSet;
			dsPI.Merge(BlackBook.LoadPrice(dsCars,ConfigBB.BlackBook.PriceTypes));
			if( addOns != null && dsPI.BookValue.Count > 0 ){
				foreach( PriceInfoDS.BookValueRow rowBV in dsPI.BookValue ){
					foreach( PriceInfoDS.AddOnsRow rowAO in dsPI.AddOns.Select("AddOnDesc IN ('"+string.Join("','",addOns)+"')") ){
						rowBV.Price += rowAO.Price;
						rowAO.Selected = true;
					}
				}
			}
			if( dsPI.BookValue.Count == 0 )dsPI.BookValue.AddBookValueRow(0,"","","",0,0);
			BlackBook.LoadCarInfo(dsCars,ref dsPI,state,mileage);
			//if( dsPI.CarInfo.Count == 0 && year == null )
			//	dsPI.CarInfo.AddCarInfoRow(applID,null,null,null,null,null,null,state,mileage,0,null);
			return dsPI;
		}
			#endregion

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a car information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">				The ds cars. </param>
		/// <param name="dsPriceInfo">	[in,out] Information describing the ds price. </param>
		/// <param name="state">				The state. </param>
		/// <param name="mileage">			The mileage. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static void LoadCarInfo(CarsDS dsCars,ref PriceInfoDS dsPriceInfo,string state,int mileage){
			if( !IsCarsDSEmpty(dsCars) )LoadCarInfo(dsCars.car[0],dsPriceInfo,state,mileage);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a car information. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="drCar">				The dr car. </param>
		/// <param name="dsPriceInfo">	Information describing the ds price. </param>
		/// <param name="state">				The state. </param>
		/// <param name="mileage">			The mileage. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static void LoadCarInfo(CarsDS.carRow drCar,PriceInfoDS dsPriceInfo,string state,int mileage){
			dsPriceInfo.CarInfo.AddCarInfoRow(drCar.vin, drCar.vin, drCar.year, drCar.make, drCar.model, drCar.series, drCar.body_style, mileage, state, int.Parse(drCar.loan_value == "0" ? drCar.msrp : drCar.loan_value), "","",PROVIDER_CODE);
		}

		#endregion

		#region AddOns

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads an add ons. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">	The ds cars. </param>
		///
		/// <returns>	The add ons. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS.AddOnsDataTable LoadAddOns(CarsDS dsCars){
			PriceInfoDS.AddOnsDataTable dtAddOns = new PriceInfoDS().AddOns;
			if( !IsCarsDSEmpty(dsCars) )
				foreach( CarsDS.optionRow row in dsCars.car[0].GetoptionsRows()[0].GetoptionRows() )
					dtAddOns.AddAddOnsRow("",row.description,"",false,GetAddOnAmount(row));
			return dtAddOns;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets an add on amount. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="row">	The row. </param>
		///
		/// <returns>	The add on amount. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static short GetAddOnAmount(CarsDS.optionRow row){
			string amount = (row.add_deduct.ToLower()=="add"?"":"-")+row.amount;
			return short.Parse(amount);
		}
		#endregion

		#region Price

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a price. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">			The ds cars. </param>
		/// <param name="priceType">	Type of the price. </param>
		///
		/// <returns>	The price. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static int GetPrice(CarsDS dsCars,string priceType){
			string price = priceType == "loan_value" ? dsCars.car[0].loan_value+"" : dsCars.wholesale_value[0][priceType]+"";
			return price != "" ? int.Parse(price) : 0;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a mileage. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">			The ds cars. </param>
		/// <param name="price">			The price. </param>
		/// <param name="priceType">	Type of the price. </param>
		///
		/// <returns>	The mileage. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static int GetMileage(CarsDS dsCars,int price,string priceType){
			double milesAddOn = 0;
			if (!double.TryParse(dsCars.mileage_range[0][priceType]+"", out milesAddOn)) return 0;
			return Convert.ToInt32(price / 2.0 < Math.Abs(milesAddOn) ? -price / 2.0 : milesAddOn);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a mileage range begin. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">	The ds cars. </param>
		///
		/// <returns>	The mileage range begin. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static int GetMileageRangeBegin(CarsDS dsCars){
			string s = dsCars.mileage_range[0].begin_range;
			return s != "" ? int.Parse(s) : 0;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Book value average. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dtBookValue">	The dt book value. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static int BookValueAvg(PriceInfoDS.BookValueDataTable dtBookValue) {
			double bv = 0;
			foreach (PriceInfoDS.BookValueRow rowBV in dtBookValue)
				bv += rowBV.Price + rowBV.MileageAddOn;
			return Convert.ToInt32(Math.Round(bv / dtBookValue.Count));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="NoMileageAddOnException">	Thrown when nomileageaddon. </exception>
		/// <exception cref="HiddenException">					Thrown when hidden. </exception>
		///
		/// <param name="dsCars">			The ds cars. </param>
		/// <param name="priceTypes">	List of types of the prices. </param>
		///
		/// <returns>	The price. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS.BookValueDataTable LoadPrice(CarsDS dsCars,params string[] priceTypes){
			try{
				if (dsCars.car.Count > 1) {
					PriceInfoDS.BookValueDataTable dtBV_Min = null;
					int bookValueMin = int.MaxValue;
					foreach (CarsDS.carRow row in dsCars.car) {
						CarsDS dsCar = BlackBook.FetchCars(row.year, row.make, row.model, row.series, row.body_style, "FL", (DateTime.Today.AddYears(-int.Parse(row.year)).Year * 15000).ToString());
						PriceInfoDS.BookValueDataTable dtBV = BlackBook.LoadPrice(dsCar,priceTypes);
						if (bookValueMin > BookValueAvg(dtBV)) {
							dtBV_Min = dtBV;
							bookValueMin = BookValueAvg(dtBV);
						}
					}
					return dtBV_Min;
				}
				PriceInfoDS.BookValueDataTable dtBookValue = new PriceInfoDS().BookValue;
				if( IsCarsDSEmpty(dsCars) )return dtBookValue;
				foreach( string priceType in priceTypes )
					LoadPrice(dsCars,dtBookValue,priceType);
				return dtBookValue;
			}catch(NoMileageAddOnException){throw;
			}catch(Exception exc){ throw new HiddenException(dsCars.GetXml(),exc); }
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads a price. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="NoMileageAddOnException">	Thrown when nomileageaddon. </exception>
		/// <exception cref="HiddenException">					Thrown when hidden. </exception>
		///
		/// <param name="dsCars">				The ds cars. </param>
		/// <param name="dtBookValue">	The dt book value. </param>
		/// <param name="priceType">		Type of the price. </param>
		///
		/// <returns>	The price. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static PriceInfoDS.BookValueDataTable LoadPrice(CarsDS dsCars,PriceInfoDS.BookValueDataTable dtBookValue,string priceType){
			try{
				int price = 0,milesAddOn = 0;
				price = GetPrice(dsCars,priceType);
				if( price > 0 && dsCars.mileage_range.Count == 1 ){
					milesAddOn = GetMileage(dsCars,price,priceType);
					//if( milesAddOn == -1 && GetMileageRangeBegin(dsCars) > 1 )price = 0;
				}
				dtBookValue.AddBookValueRow(price,priceType,PROVIDER_CODE,dsCars.car[0].vin,milesAddOn,0);
				return dtBookValue;
			}catch(NoMileageAddOnException){throw;
			}catch(Exception exc){ throw new HiddenException(dsCars.GetXml(),exc); }
		}
		#endregion


		#region Fetch/Load from BlackBook

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Fetches the cars. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="year">			The year. </param>
		/// <param name="make">			The make. </param>
		/// <param name="model">		The model. </param>
		/// <param name="series">		The series. </param>
		/// <param name="style">		The style. </param>
		/// <param name="state">		The state. </param>
		/// <param name="mileage">	The mileage. </param>
		///
		/// <returns>	The cars. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static CarsDS FetchCars(string year,string make,string model,string series,string style,string state,string mileage){
			CarsDS dsCars = Fetch("year",year,"make",make,"model",model,"series",series,"body_style",style,"state",state,"mileage",mileage);
			foreach( CarsDS.carRow row in dsCars.car.Select("body_style <> '"+style+"' OR body_style IS NULL") )
				row.Delete();
			return dsCars;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Fetches the cars. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="vin">			The vin. </param>
		/// <param name="state">		The state. </param>
		/// <param name="mileage">	The mileage. </param>
		///
		/// <returns>	The cars. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static CarsDS FetchCars(string vin,string state,string mileage){
			return Fetch("vin",vin,"state",state,"mileage",mileage);
			//if( dsCars.car.Count == 0 )return dsCars;
			//CarsDS.carDataTable dtCar = dsCars.car;
			//CarsDS.carRow rowMin = dtCar[0];
			//while( dtCar.Count > 1  ){
			//	CarsDS.carRow rowCar  = dtCar[1];
			//	if( int.Parse(rowCar.Getwholesale_valueRows()[0].clean) < int.Parse(rowMin.Getwholesale_valueRows()[0].clean) ){
			//		rowMin.Delete();
			//		rowMin = rowCar;
			//	}else rowCar.Delete();
			//}
			//return dsCars;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads the styles. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="WebException">	Thrown when web. </exception>
		///
		/// <param name="year">		The year. </param>
		/// <param name="make">		The make. </param>
		/// <param name="model">	The model. </param>
		/// <param name="series">	The series. </param>
		///
		/// <returns>	The styles. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string[] LoadStyles(string year,string make,string model,string series){
			try{
				CarsDS dsCars = Fetch("year",year,"make",make,"model",model,"series",series);
				return GetCarParamArray(dsCars,"body_style");
			}catch(WebException exc){
				if( exc.Message.IndexOf("500") < 0 || year != "2002" )throw;
			}
			return new string[]{};
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads the series. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="year">		The year. </param>
		/// <param name="make">		The make. </param>
		/// <param name="model">	The model. </param>
		///
		/// <returns>	The series. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string[] LoadSeries(string year,string make,string model){
			CarsDS dsCars = Fetch("year",year,"make",make,"model",model);
			string[] s = GetCarParamArray(dsCars,"series");
			return s.Length > 0 ? s : new string[]{""};
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Loads the models. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="year">	The year. </param>
		/// <param name="make">	The make. </param>
		///
		/// <returns>	The models. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string[] LoadModels(string year,string make){
			CarsDS dsCars = Fetch("year",year,"Make",make);
			return GetCarParamArray(dsCars,"model");
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a car parameter array. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="dsCars">			The ds cars. </param>
		/// <param name="columnName">	Name of the column. </param>
		///
		/// <returns>	The car parameter array. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string[] GetCarParamArray(CarsDS dsCars,string columnName){
			StringList sl = new StringList();
			foreach( CarsDS.carRow rowCar in dsCars.car )
				if( rowCar[columnName].ToString() != "" )
					sl.Add(rowCar[columnName]+"");
			return sl;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Fetches. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="WebException">					Thrown when web. </exception>
		/// <exception cref="ApplicationException">	Thrown when application. </exception>
		///
		/// <param name="nameValue">	The params string to fetch. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static CarsDS Fetch(params string[] nameValue){
			WebClient webClient = new WebClient();
			string strResp = "";
			CarsDS dsCars = new CarsDS();
			NameValueCollectionEx queryString = new NameValueCollectionEx(nameValue);
			foreach( string name in queryString.AllKeys )
				if( queryString[name] != null )queryString[name] = queryString[name].Replace("&","%26");
			try{
				webClient.QueryString = queryString;
				strResp = Encoding.ASCII.GetString(webClient.DownloadData(ConfigBB.BlackBook.URL));
				Debug.WriteLine(strResp);
				SQLHelperEx.LoadCustomDataSet(dsCars,strResp);
			}catch(WebException exc){
				if( exc.Message.IndexOf("500") < 0 || queryString["year"] != "2002" )throw;
			}catch(Exception exc){throw new ApplicationException("\nQuery->"+queryString+"\n",exc);}
			return dsCars;
		}
		#endregion

		#region HTML

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a select option. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="select">	The select. </param>
		/// <param name="name">		The name. </param>
		///
		/// <returns>	The select option. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string GetSelectOption(string select,string name){
			string reStr = "<select.*?name=\".*"+name+"\".+?>([^~]*?)</select>";
			Regex re;
			re = new Regex(reStr,RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
			Match m = re.Match(select);
			if( !m.Success )return "?";// throw new ASPException("No selection found for ["+name+"]");
			m = Regex.Match(m.Groups[1].Value," selected .+?\"(.*?)\"",RegexOptions.IgnoreCase);
			return m.Groups[1].Value;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a select options. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="select">	The select. </param>
		/// <param name="name">		The name. </param>
		///
		/// <returns>	The select options. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string[] GetSelectOptions(string select,string name){
			string reStr = "<select.*?name=\".*"+name+"\".+?>([^~]*?)</select>";
			Regex re;
			re = new Regex(reStr,RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
			Match m = re.Match(select);
			if( !m.Success )return new string[]{"?"};// throw new ASPException("No selection found for ["+name+"]");
			MatchCollection mc = Regex.Matches(m.Groups[1].Value,"\"(.*?)\"",RegexOptions.Multiline);
			string[] opts = new string[mc.Count];
			for( int i=0; i < mc.Count; i++ )
				opts[i] = mc[i].Groups[1].Value;
			return opts;
		}
		#endregion HTML

		#region HTTP
			#region Post

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="values">	The values. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string Post(NameValueCollection values){
			return Post(queryStringBase,values);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="queryString">	The query string. </param>
		/// <param name="values">				The values. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string Post(NameValueCollection queryString,NameValueCollection values){
			return Post("POST",queryString,values);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="method">				The method. </param>
		/// <param name="queryString">	The query string. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string Post(string method,NameValueCollection queryString){
			return Post(method,queryString,null);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="method">				The method. </param>
		/// <param name="queryString">	The query string. </param>
		/// <param name="values">				The values. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string Post(string method,NameValueCollection queryString,NameValueCollection values){
			return Post(BB_PATH_USED,method,queryString,values);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="action">				The action. </param>
		/// <param name="method">				The method. </param>
		/// <param name="queryString">	The query string. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string Post(string action,string method,NameValueCollection queryString){
			return Post(action,method,queryString,null);
		}
		//http://www.blackbookusa.com/bb/usedcar.asp?tvin=&savebodystyle=&bb_year=2000&bb_make=BUICK&save_vin=&appraisal=0&saveprint=

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Post this message. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="ASPException">	Thrown when asp. </exception>
		///
		/// <param name="action">				The action. </param>
		/// <param name="method">				The method. </param>
		/// <param name="queryString">	The query string. </param>
		/// <param name="values">				The values. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		[MethodImplAttribute(MethodImplOptions.Synchronized)]
		private static string Post(string action,string method,NameValueCollection queryString,NameValueCollection values){
			WebClient webClient = new WebClient();
			try{
				if( values == null ){
					values = new NameValueCollection();
					values.Add(KEY_FILE_NAME,"Get");
				}
				webClient.BaseAddress = BB_URL_ROOT;
				webClient.QueryString = queryString;
				string strResp = "";
				for( int i = 3; i > 0 && strResp == ""; i-- )
					try{
						strResp = Encoding.ASCII.GetString(webClient.UploadValues(action,method,values));
					}catch(WebException){ Thread.Sleep(500);}
				SaveToFile(@"c:\"+FILE_NAME_PREFIX+values[KEY_FILE_NAME]+".htm",strResp);
				return strResp;
			}catch(Exception exp){
				throw new ASPException("\nAction:"+action+",Method:"+method+"\nQuery->"+ShowNVC(queryString)+"\nValues->"+ShowNVC(values)+"\n",exp);
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Shows the nvc. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="nvc">	The nvc. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string ShowNVC(NameValueCollection nvc){
			if( nvc == null )return "";
			ArrayList al = new ArrayList();
			foreach( string key in nvc.Keys )
				al.Add(key+":"+nvc[key]);
			return string.Join("\n\t",(string[])al.ToArray(typeof(System.String)));
		}
			#endregion Post

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Log on. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="ASPException">	Thrown when asp. </exception>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static void LogOn(){
			string action = "../logon.asp?func=1";
			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("Logon_Id","affil");
			nvc.Add("Password","13752");
			string response = Post(action,"POST",nvc);
			cntl = GetFormAction(response);
			if( Regex.Match(response,"Your account is currently being used by another user, please try again later",RegexOptions.IgnoreCase).Success )
				throw new ASPException("Account is busy. Try again.");
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Saves to file. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <param name="fileName">	Filename of the file. </param>
		/// <param name="text">			The text. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static void SaveToFile(string fileName,string text){
			StreamWriter sw = new StreamWriter(fileName,false);
			text = Regex.Replace(text,"<head","<BASE href='"+urlUsed+"'/><head",RegexOptions.IgnoreCase);
//			text = Regex.Replace(text,"method=\"POST\"","method=\"GET\"",RegexOptions.IgnoreCase);
			sw.Write(text);//.replace(/method=["]*post["]*/i,"method='post'"););
			sw.Close();
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a form action. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		///
		/// <exception cref="ASPException">	Thrown when asp. </exception>
		///
		/// <param name="response">	The response. </param>
		///
		/// <returns>	The form action. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string GetFormAction(string response){
			Match a = Regex.Match(response,"<form.+action=\"(.+?)\"",RegexOptions.IgnoreCase);
			if( !a.Success )throw new ASPException("Error on page.\n"+response);
			return a.Groups[1].Value;
		}
	#endregion HTTP

		#region Private properties ===============================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the url used. </summary>
		///
		/// <value>	The url used. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string urlUsed{get{return BB_URL_ROOT+BB_PATH_USED;}}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the query string base. </summary>
		///
		/// <value>	The query string base. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static NameValueCollection queryStringBase{
			get{
				NameValueCollection nvc = new NameValueCollection();
				nvc.Add("func","100");
				nvc.Add("color","");
				nvc.Add("cntl",cntl);
				return nvc;
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets or sets the cntl. </summary>
		///
		/// <value>	The cntl. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string cntl{
			get{
				if( m_cntl == string.Empty ){
					//				Match m = Regex.Match(value,"cntl=(\\w+)",RegexOptions.IgnoreCase);
					//				if( !m.Success )throw new ASPException("No \"cntl\" parameter in Logon Response.\n"+value);
					//				HttpContext.Current.Application.Contents[BB_SessionID] = m.Groups[1].Value;
				}
				return m_cntl;
			}
			set{m_cntl = value;}
		}

		#endregion Private properties ============================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Exception for signalling no mileage add on errors. </summary>
		///
		/// <remarks>	Dimon, 11/23/2010. </remarks>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private class NoMileageAddOnException:Exception{}
	}
}
